﻿/*******************************************************************************
 * AAEngine
 * Copyright (c) 2010 Mike Jarosch
 *
 * Original source PushButton Engine:
 * Copyright (C) 2009 PushButton Labs, LLC
 * For more information see http://www.pushbuttonengine.com
 *
 * This file is licensed under the terms of the MIT license, which is included
 * in the "MIT License.txt" file at the root directory of this SDK.
 ******************************************************************************/

using Microsoft.Xna.Framework;
using AAEngine.Engine;
using AAEngine.Engine.Components;
using AAEngine.Engine.Core;
using AAEngine.Engine.Entities;

namespace AAEngine.Rendering2D.Renderers
{
    /// <summary>
    /// 
    /// </summary>
    /// <remarks>
    /// Normally, the RendererBase tries to update itself
    /// every frame. However, you can suppress this by setting
    /// RegisterForUpdates to false, in which case you will have to
    /// call OnFrame()/UpdateTransform() manually if you change
    /// something.
    /// </remarks>
    public abstract class RendererBase : AnimatedComponent
    {
        protected IScene2D _scene;

        protected int _layerIndex;
        protected int _lastLayerIndex;
        protected int _zIndex;

        protected float _rotationOffset;
        protected Vector2 _registrationPoint = new Vector2();
        protected Vector2 _scale = Vector2.One;
        protected float _rotation;
        protected float _alpha = 1f;
        protected Vector2 _position = new Vector2();
        protected Vector2 _positionOffset = new Vector2();
        protected Vector2? _size;

        protected bool _transformDirty = true;
        protected bool _layerIndexDirty = true;
        protected bool _zIndexDirty = true;

        protected bool _inScene;

        protected Matrix _transformationMatrix = new Matrix();

        public RendererBase()
        {
            SnapToNearestPixesl = false;
        }

        /// <summary>
        /// If set, position is gotten from this property every frame.
        /// </summary>
        public virtual PropertyReference PositionProperty { get; set; }

        /// <summary>
        /// if set this to false, positions will be handeled with floats insteed of integers
        /// makes slow movement smoother for example
        /// </summary>
        public virtual bool SnapToNearestPixesl { get; set; }

        /// <summary>
        /// If set, size is determined by this property every frame.
        /// </summary>
        public virtual PropertyReference SizeProperty { get; set; }

        /// <summary>
        /// If set, rotation is gotten from this property every frame.
        /// </summary>
        public virtual PropertyReference RotationProperty { get; set; }

        /// <summary>
        /// If set, alpha is gotten from this property every frame.
        /// </summary>
        public virtual PropertyReference AlphaProperty { get; set; }

        /// <summary>
        /// If set, the layer index is gotten from this property every frame.
        /// </summary>
        public virtual PropertyReference LayerIndexProperty { get; set; }

        /// <summary>
        /// If set, our z-index is gotten from this property every frame.
        /// </summary>
        public virtual PropertyReference ZIndexProperty { get; set; }

        /// <summary>
        /// If set, our registration point is gotten from this property every frame.
        /// </summary>
        /// <remarks>
        /// Note that some subclasses override this; for instance, the
        /// SpriteSheetRenderer always uses the registrationPoint from the
        /// sprite sheet it is rendering.
        /// </remarks>
        public virtual PropertyReference RegistrationPointProperty { get; set; }

        /// <summary>
        /// The types for this object; used for picking queries primarily.
        /// </summary>
        public virtual ObjectType ObjectMask { get; set; }

        /// <summary>
        /// In what layer of the scene is this renderer drawn?
        /// </summary>
        public virtual int LayerIndex
        {
            get { return _layerIndex; }
            set
            {
                if (_layerIndex == value)
                {
                    return;
                }

                _layerIndex = value;
                _layerIndexDirty = true;
            }
        }

        /// <summary>
        /// By default, layers are sorted based on the z-index, from small
        /// to large.
        /// </summary>
        public virtual int ZIndex
        {
            get { return _zIndex; }
            set
            {
                if (_zIndex == value)
                {
                    return;
                }

                _zIndex = value;
                _zIndexDirty = true;
            }
        }

        /// <summary>
        /// The registration point can be used to offset the sprite
        /// so that rotation and scaling work properly.
        /// </summary>
        public virtual Vector2 RegistrationPoint
        {
            get { return _registrationPoint; }
            set
            {
                int intX = (int)value.X;
                int intY = (int)value.Y;

                if (intX == _registrationPoint.X && intY == _registrationPoint.Y)
                {
                    return;
                }

                _registrationPoint.X = intX;
                _registrationPoint.Y = intY;
                _transformDirty = true;
            }
        }

        public virtual Vector2 Scale
        {
            get { return _scale; }
            set
            {
                if (value.X == _scale.X && value.Y == _scale.Y)
                {
                    return;
                }

                _scale.X = value.X;
                _scale.Y = value.Y;
                _transformDirty = true;
            }
        }

        /// <summary>
        /// Explicitly set the size. This overrides scale if it is set.
        /// </summary>
        public virtual Vector2? Size
        {
            get { return _size; }
            set
            {
                _size = value;
                _transformDirty = true;
            }
        }

        /// <summary>
        /// Rotation in degrees, with 0 being Y+.
        /// </summary>
        public virtual float Rotation
        {
            get { return _rotation; }
            set
            {
                if (_rotation == value)
                {
                    return;
                }

                _rotation = value;
                _transformDirty = true;
            }
        }

        /// <summary>
        /// Transparency, 0 being completely transparent and 1 being opaque.
        /// </summary>
        public virtual float Alpha
        {
            get { return _alpha; }
            set
            {
                if (_alpha == value)
                {
                    return;
                }

                _alpha = value;
                _transformDirty = true;
            }
        }

        /// <summary>
        /// Sets a position offset that will offset the sprite.
        /// </summary>
        public virtual Vector2 PositionOffset
        {
            get { return _positionOffset; }
            set
            {
                if (value.X == _positionOffset.X && value.Y == _positionOffset.Y)
                {
                    return;
                }

                _positionOffset.X = value.X;
                _positionOffset.Y = value.Y;
                _transformDirty = true;
            }
        }

        /// <summary>
        /// Position of the renderer in scene space.
        /// </summary>
        public virtual Vector2 Position
        {
            get { return _position; }
            set
            {
                float posX, posY;
                if (SnapToNearestPixesl)
                {
                    posX = (int)value.X;
                    posY = (int)value.Y;
                }
                else
                {
                    posX = value.X;
                    posY = value.Y;
                }

                if (posX == _position.X && posY == _position.Y)
                {
                    return;
                }

                _position.X = posX;
                _position.Y = posY;
                _transformDirty = true;
            }
        }

        /// <summary>
        /// The x value of our scene space position.
        /// </summary>
        public virtual float X
        {
            get { return _position.X; }
            set
            {
                float posX;
                if (SnapToNearestPixesl)
                {
                    posX = (int)value;
                }
                else
                {
                    posX = value;
                }

                if (posX == _position.X)
                {
                    return;
                }

                _position.X = posX;
                _transformDirty = true;
            }
        }

        /// <summary>
        /// The y component of our scene space position. Used for sorting.
        /// </summary>
        public virtual float Y
        {
            get { return _position.Y; }
            set
            {
                float posY;
                if (SnapToNearestPixesl)
                {
                    posY = (int)value;
                }
                else
                {
                    posY = value;
                }

                if (posY == _position.Y)
                {
                    return;
                }

                _position.Y = posY;
                _transformDirty = true;
            }
        }
        public IScene2D Scene
        {
            get { return _scene; }
            set
            {
                RemoveFromScene();

                _scene = value;

                AddToScene();
            }
        }

        override public void OnAdd()
        {
            base.OnAdd();

            AddToScene();

            UpdateTransform(true);
        }

        override public void OnRemove()
        {
            RemoveFromScene();

            base.OnRemove();
        }

        void AddToScene()
        {
            if (_scene != null && !_inScene)
            {
                _scene.AddRenderable(this);
                _inScene = true;

                _lastLayerIndex = _layerIndex;
                _layerIndexDirty = _zIndexDirty = true;
            }
        }

        void RemoveFromScene()
        {
            if (_scene != null && _inScene)
            {
                _scene.RemoveRenderable(this);
                _inScene = false;
            }
        }

        public override void OnFrame(float deltaTime)
        {
            base.OnFrame(deltaTime);

            UpdateProperties();

            if (_transformDirty)
            {
                UpdateTransform();
            }
        }

        protected void UpdateProperties()
        {
            if (Owner == null)
            {
                return;
            }

            if (ZIndexProperty != null)
            {
                ZIndex = (int)Owner.GetProperty(ZIndexProperty, ZIndex);
            }

            if (LayerIndexProperty != null)
            {
                LayerIndex = (int)Owner.GetProperty(LayerIndexProperty);
            }

            if (_zIndexDirty && _scene != null)
            {
                _scene.GetLayer(_layerIndex, true).MarkDirty();
                _zIndexDirty = false;
            }

            Vector2? pos = Owner.GetProperty(PositionProperty) as Vector2?;
            if (pos.HasValue)
            {
                Position = pos.Value;
            }

            Vector2? size = Owner.GetProperty(SizeProperty) as Vector2?;
            if (size.HasValue)
            {
                Size = size.Value;
            }

            float? rotation = Owner.GetProperty(RotationProperty) as float?;
            if (rotation.HasValue)
            {
                Rotation = rotation.Value;
            }

            float? alpha = Owner.GetProperty(AlphaProperty) as float?;
            if (alpha.HasValue)
            {
                Alpha = alpha.Value;
            }

            Vector2? reg = Owner.GetProperty(RegistrationPointProperty) as Vector2?;
            if (reg.HasValue)
            {
                RegistrationPoint = reg.Value;
            }

            // Make sure we're in the right layer and at the right zIndex in the scene.
            // Do this last to be more caching-layer-friendly. If we change position and
            // layer we can just do this at end and it works.
            if (_layerIndexDirty && _scene != null)
            {
                int tmp = _layerIndex;
                _layerIndex = _lastLayerIndex;

                if (_lastLayerIndex != -1)
                {
                    RemoveFromScene();
                }

                _layerIndex = tmp;

                AddToScene();

                _lastLayerIndex = _layerIndex;
                _layerIndexDirty = false;
            }
        }

#if WINDOWS_PHONE || XBOX
        public void UpdateTransform()
        {
            UpdateTransform(false);
        }

        public void UpdateTransform(bool updateProperties)
#else
        public void UpdateTransform(bool updateProperties = false)
#endif
        {
            if (updateProperties)
            {
                UpdateProperties();
            }

            _transformationMatrix = Matrix.Identity;
            _transformationMatrix *= Matrix.CreateTranslation(-_registrationPoint.X, -_registrationPoint.Y, 0);
            _transformationMatrix *= Matrix.CreateRotationZ(MathHelper.ToRadians(_rotation) + _rotationOffset);
            _transformationMatrix *= Matrix.CreateTranslation(_position.X + _positionOffset.X, _position.Y + _positionOffset.Y, 0);

            _transformDirty = false;
        }

        public abstract void Render(Box worldExtents, RenderQueue renderQueue);

        public abstract Box WorldExtents { get; }
    }
}
